﻿/*
 * Written and tested by Gilnaa,
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OldMath = System.Math;

namespace MadLibrary.Math
{
    /// <summary>
    /// Represents a 2 dimensional vector.
    /// </summary>
    public struct Vector2D
    {
        //Properties
        /// <summary>
        /// Gets the length of this vector along the X axis.
        /// </summary>
        public double X { get; private set; }

        /// <summary>
        /// Gets the length of this vector along the Y axis.
        /// </summary>
        public double Y { get; private set; }

        /// <summary>
        /// Gets or sets the force of this vecotr (In newtons).
        /// </summary>
        public double Force { get; private set; }

        /// <summary>
        /// Gets or sets the angle of this vector (In radians) from the positive X axis.
        /// </summary>
        public double Angle { get; private set; }

        /// <summary>
        /// Gets the refined value of the vector's angle.
        /// </summary>
        /// <example>
        /// -2PI, 2PI, -4PI, 4PI, etc... -> 0;
        /// 5PI -> PI;
        /// -5PI -> -PI;
        /// and so on.
        /// </example>
        public double RefinedAngle { get; private set; }

        /// <summary>
        /// Gets a number indecating the quarter the Vector points to.
        /// </summary>
        /// <remarks>Returns a number between 0 and 3.</remarks>
        public int Quarter { get; private set; }

        //Constructor.
        /// <summary>
        /// Creates a new instance of <see cref="Vector2D"/> with the specified force and angle.
        /// </summary>
        /// <param name="f">The force of the vector (in newtons).</param>
        /// <param name="a">The angle of the vector (in radians).</param>
        public Vector2D(double f, double a)
            : this()
        {
            Force = f;
            Angle = a;

            RefinedAngle = Angle;
            while (RefinedAngle >= MathHelper.TwoPI)
                RefinedAngle -= MathHelper.TwoPI;
            while (RefinedAngle <= -MathHelper.TwoPI)
                RefinedAngle += MathHelper.TwoPI;

            Quarter = (int)(RefinedAngle / MathHelper.HalfPI);

            if (Quarter == 0 || Quarter == 2)
            {
                double qAngle = RefinedAngle;
                if (qAngle > MathHelper.HalfPI)
                    qAngle -= MathHelper.PI;
                X = OldMath.Cos(qAngle) * Force;
                Y = OldMath.Sin(qAngle) * Force;
            }
            else
            {
                double qAngle = RefinedAngle - MathHelper.HalfPI;
                if (qAngle > MathHelper.HalfPI)
                    qAngle -= MathHelper.PI;
                X = OldMath.Sin(qAngle) * Force;
                Y = OldMath.Cos(qAngle) * Force;
            }
        }

        /// <summary>Query if this object is valid.</summary>
        /// <returns>true if valid, false if not.</returns>
        public bool IsValid()
        {
            return
                !double.IsNaN(X) && !double.IsInfinity(X) &&
                !double.IsNaN(Y) && !double.IsInfinity(Y);
        }

        //Methods
        /// <summary>
        /// Returns the HashCode for this instance.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return 7 * Force.GetHashCode() + Angle.GetHashCode();
        }

        /// <summary>
        /// Determines wether the specified System.Object equals to the current Vector2D.
        /// </summary>
        /// <param name="obj">The System.Object to test for equality.</param>
        /// <returns>True if the given object is a Vector2D that is equal to this Vector2D, false otherwise.</returns>
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Vector2D))
                return false;

            Vector2D _obj = (Vector2D)obj;
            return (_obj.Angle == Angle && _obj.Force == Force);
        }

        /// <summary>
        /// Returns a string represnting the Vector2D
        /// </summary>
        /// <returns>A <see cref="System.String"/></returns>
        public override string ToString()
        {
            return String.Format("[{0}  Force:{1},{0}  Angle:{2}(Refined: {3}),{0}  X:{4},{0}  Y:{5}{0}]", Environment.NewLine, this.Force, this.Angle, this.RefinedAngle, this.X, this.Y);
        }

        //Static
        /// <summary>
        /// Adds two vectors
        /// </summary>
        /// <param name="a">The first vector</param>
        /// <param name="b">The second vector</param>
        /// <returns>A <see cref="Vector2D"/>. The sum of the two parameters.</returns>
        public static Vector2D operator +(Vector2D a, Vector2D b)
        {
            Vector2D c = new Vector2D();
            double newX = a.X + b.X;
            double newY = a.Y + b.Y;
            double newF = OldMath.Sqrt(
                OldMath.Pow(newX, 2) + OldMath.Pow(newY, 2)
                                          );
            double newAngle = OldMath.Atan(newY / newX);
            c.Force = newF;
            c.Angle = newAngle;
            return c;
        }

        /// <summary>
        /// Subtracts the second vector from the first vecotr.
        /// </summary>
        /// <param name="a">The minuend vector.</param>
        /// <param name="b">The subtrahend vector.</param>
        /// <returns>A <see cref="Vector2D"/>. The difference between the two vector.</returns>
        public static Vector2D operator -(Vector2D a, Vector2D b)
        {
            Vector2D c = new Vector2D();
            double newX = a.X - b.X;
            double newY = a.Y - b.Y;
            double newF = OldMath.Sqrt(
                OldMath.Pow(newX, 2) + OldMath.Pow(newY, 2)
                                          );
            double newAngle = OldMath.Atan(newY / newX);
            c.Force = newF;
            c.Angle = newAngle;
            return c;
        }

        /// <summary>
        /// Flips the given vector.
        /// </summary>
        /// <param name="a">The vector to flip.</param>
        /// <returns>A <see cref="Vector2D"/> pointing to the other direction than the given one.</returns>
        public static Vector2D operator -(Vector2D a)
        {
            return new Vector2D(a.Force, a.Angle + MathHelper.PI);
        }

        /// <summary>
        /// Checks if the two given vectors are equal.
        /// </summary>
        public static bool operator ==(Vector2D a, Vector2D b)
        {
            return a.Equals(b);
        }

        /// <summary>
        /// Checks if the two given vectors are not equal.
        /// </summary>
        public static bool operator !=(Vector2D a, Vector2D b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Creates a new vector from its components.
        /// </summary>
        /// <param name="X">The X component.</param>
        /// <param name="Y">The Y component.</param>
        /// <returns>A <see cref="Vector2D"/></returns>
        public static Vector2D CreateXY(double X, double Y)
        {
            return new Vector2D(X, 0) + new Vector2D(Y, MathHelper.HalfPI);
        }

        /// <summary>
        /// An empty <see cref="Vector2D"/>
        /// </summary>
        public static readonly Vector2D Zero = new Vector2D();

        /// <summary>
        /// A vector with the value of 1 pointing along the X axis.
        /// </summary>
        public static readonly Vector2D XNormal = new Vector2D(1, 0);

        /// <summary>
        /// A vector with the value of 1 pointing along the Y axis.
        /// </summary>
        public static readonly Vector2D YNormal = new Vector2D(1, MathHelper.HalfPI);
    }
}